winsafe\kernel\utilities/path.rs
1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory. Does not
12/// search recursively.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// ```no_run
20/// use winsafe::{self as w, prelude::*};
21///
22/// // Ordinary for loop
23/// for file_path in w::path::dir_list_flat("C:\\Temp") {
24/// let file_path = file_path?;
25/// println!("{}", file_path);
26/// }
27///
28/// // Collecting into a Vec
29/// let all = w::path::dir_list_flat("C:\\Temp")
30/// .collect::<w::SysResult<Vec<_>>>()?;
31///
32/// // Transforming and collecting into a Vec
33/// let all = w::path::dir_list_flat("C:\\Temp")
34/// .map(|file_path| {
35/// let file_path = file_path?;
36/// Ok(format!("PATH: {}", file_path))
37/// })
38/// .collect::<w::SysResult<Vec<_>>>()?;
39/// # w::SysResult::Ok(())
40/// ```
41#[must_use]
42pub fn dir_list_flat<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
43 DirListFlatIter::new(dir_path.to_owned())
44}
45
46/// Returns an interator over the files within a directory, and all its
47/// subdirectories, recursively.
48///
49/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
50/// iteration functions.
51///
52/// # Examples
53///
54/// ```no_run
55/// use winsafe::{self as w, prelude::*};
56///
57/// // Ordinary for loop
58/// for file_path in w::path::dir_list_recursive("C:\\Temp") {
59/// let file_path = file_path?;
60/// println!("{}", file_path);
61/// }
62///
63/// // Closure with try_for_each
64/// w::path::dir_list_recursive("C:\\Temp")
65/// .try_for_each(|file_path| {
66/// let file_path = file_path?;
67/// println!("{}", file_path);
68/// Ok(())
69/// })?;
70///
71/// // Collecting into a Vec
72/// let all = w::path::dir_list_recursive("C:\\Temp")
73/// .collect::<w::SysResult<Vec<_>>>()?;
74///
75/// // Transforming and collecting into a Vec
76/// let all = w::path::dir_list_recursive("C:\\Temp")
77/// .map(|file_path| {
78/// let file_path = file_path?;
79/// Ok(format!("PATH: {}", file_path))
80/// })
81/// .collect::<w::SysResult<Vec<_>>>()?;
82/// # w::SysResult::Ok(())
83/// ```
84#[must_use]
85pub fn dir_list_recursive<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
86 DirListRecursiveIter::new(dir_path.to_owned())
87}
88
89/// Returns a new string with the path of the current EXE file, without the EXE
90/// filename, and without a trailing backslash.
91///
92/// In a debug build, the `target\debug` folders will be suppressed.
93#[cfg(debug_assertions)]
94#[must_use]
95pub fn exe_path() -> SysResult<String> {
96 let dbg = HINSTANCE::NULL.GetModuleFileName()?;
97 Ok(get_path(
98 get_path(
99 get_path(&dbg).unwrap(), // exe name; go up target and debug folders
100 )
101 .unwrap(),
102 )
103 .unwrap()
104 .to_owned())
105}
106
107/// Returns a new string with the path of the current EXE file, without the EXE
108/// filename, and without a trailing backslash.
109///
110/// In a debug build, the `target\debug` folders will be suppressed.
111#[cfg(not(debug_assertions))]
112#[must_use]
113pub fn exe_path() -> SysResult<String> {
114 Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
115 .unwrap()
116 .to_owned())
117}
118
119/// Returns true if the path exists.
120#[must_use]
121pub fn exists(full_path: &str) -> bool {
122 GetFileAttributes(full_path).is_ok()
123}
124
125/// Extracts the file name from a full path, if any.
126///
127/// # Examples
128///
129/// ```no_run
130/// use winsafe::{self as w, prelude::*};
131///
132/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
133/// ```
134#[must_use]
135pub fn get_file_name(full_path: &str) -> Option<&str> {
136 match full_path.rfind('\\') {
137 None => Some(full_path), // if no backslash, the whole string is the file name
138 Some(idx) => {
139 if idx == full_path.chars().count() - 1 {
140 None // last char is '\\', no file name
141 } else {
142 Some(&full_path[idx + 1..])
143 }
144 },
145 }
146}
147
148/// Extracts the full path, but the last part.
149///
150/// # Examples
151///
152/// ```no_run
153/// use winsafe::{self as w, prelude::*};
154///
155/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
156/// let q = w::path::get_path("C:\\Temp\\xx\\"); // C:\Temp\xx
157/// let r = w::path::get_path("C:\\Temp\\xx"); // C:\Temp"
158/// ```
159#[must_use]
160pub fn get_path(full_path: &str) -> Option<&str> {
161 full_path
162 .rfind('\\') // if no backslash, the whole string is the file name, so no path
163 .map(|idx| &full_path[0..idx])
164}
165
166/// Tells whether the full path ends in one of the given extensions,
167/// case-insensitive.
168///
169/// # Examples
170///
171/// ```no_run
172/// use winsafe::{self as w, prelude::*};
173///
174/// println!("{}",
175/// w::path::has_extension("file.txt", &["txt", "bat"]));
176/// ```
177#[must_use]
178pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
179 let full_path_u = full_path.to_uppercase();
180 extensions
181 .iter()
182 .find(|ext| {
183 let mut ext_upper = ext.as_ref().to_uppercase();
184 if !ext_upper.starts_with('.') {
185 ext_upper.insert(0, '.'); // prepend dot
186 }
187 full_path_u.ends_with(&ext_upper)
188 })
189 .is_some()
190}
191
192/// Returns true if the path is a directory. Calls
193/// [`GetFileAttributes`](crate::GetFileAttributes).
194///
195/// # Panics
196///
197/// Panics if the path does not exist.
198#[must_use]
199pub fn is_directory(full_path: &str) -> bool {
200 let flags = GetFileAttributes(full_path).unwrap();
201 flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
202}
203
204/// Returns true if the path is hidden. Calls
205/// [`GetFileAttributes`](crate::GetFileAttributes).
206///
207/// # Panics
208///
209/// Panics if the path does not exist.
210#[must_use]
211pub fn is_hidden(full_path: &str) -> bool {
212 let flags = GetFileAttributes(full_path).unwrap();
213 flags.has(co::FILE_ATTRIBUTE::HIDDEN)
214}
215
216/// Replaces the file extension by the given one, returning a new string.
217///
218/// # Examples
219///
220/// ```no_run
221/// use winsafe::{self as w, prelude::*};
222///
223/// let p = w::path::replace_extension(
224/// "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
225/// ```
226#[must_use]
227pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
228 if let Some(last) = full_path.chars().last() {
229 if last == '\\' {
230 return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
231 }
232 }
233
234 let new_has_dot = new_extension.chars().next() == Some('.');
235 match full_path.rfind('.') {
236 None => format!(
237 "{}{}{}", // file name without extension, just append it
238 full_path,
239 if new_has_dot { "" } else { "." },
240 new_extension,
241 ),
242 Some(idx) => {
243 format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
244 },
245 }
246}
247
248/// Replaces the file name by the given one, returning a new string.
249#[must_use]
250pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
251 match get_path(full_path) {
252 None => new_file.to_owned(),
253 Some(path) => format!("{}\\{}", path, new_file),
254 }
255}
256
257/// Keeps the file name and replaces the path by the given one, returning a new
258/// string.
259///
260/// # Examples
261///
262/// ```no_run
263/// use winsafe::{self as w, prelude::*};
264///
265/// let p = w::path::replace_path( // C:\another\foo.txt
266/// "C:\\Temp\\foo.txt",
267/// "C:\\another",
268/// );
269/// ```
270#[must_use]
271pub fn replace_path(full_path: &str, new_path: &str) -> String {
272 let file_name = get_file_name(full_path);
273 format!(
274 "{}{}{}",
275 rtrim_backslash(new_path),
276 if file_name.is_some() { "\\" } else { "" },
277 file_name.unwrap_or("")
278 )
279}
280
281/// Removes a trailing backslash, if any.
282///
283/// # Examples
284///
285/// ```no_run
286/// use winsafe::{self as w, prelude::*};
287///
288/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
289/// ```
290#[must_use]
291pub fn rtrim_backslash(full_path: &str) -> &str {
292 match full_path.chars().last() {
293 None => full_path, // empty string
294 Some(last_ch) => {
295 if last_ch == '\\' {
296 let mut chars = full_path.chars();
297 chars.next_back(); // remove last char
298 chars.as_str()
299 } else {
300 full_path // no trailing backslash
301 }
302 },
303 }
304}
305
306/// Returns a `Vec` with each part of the full path.
307#[must_use]
308pub fn split_parts(full_path: &str) -> Vec<&str> {
309 let no_bs = rtrim_backslash(full_path);
310 no_bs.split('\\').collect()
311}